package net.vinote.smart.platform.module.dbpool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;

import net.vinote.smart.platform.module.log.RunLogger;

/**
 * 数据库连接池
 * 
 * @author 郑俊伟
 * 
 */
class DBConnectionPool {
	private static final RunLogger logger = RunLogger.getLogger();
	/**
	 * 数据库连接回收线程名
	 */
	public static final String POOL_RECYCLE_THREAD_NAME = "DBConnectRecycleThread";
	/**
	 * 常驻连接池对象
	 */
	private List<ProxyConnection> permConnList;

	/** 常驻连接池游标 */
	private int cursor = 0;

	/**
	 * 临时链接对象池
	 */
	private ConcurrentLinkedQueue<ProxyConnection> tempConnList;

	private Object lock = new Object();

	/** 数据库连接池配置参数 */
	private ConnectionParam param;

	/**
	 * 数据库连接回收线程
	 */
	private PoolRecycleThread recycleThread;

	/** 是否可用 */
	private volatile boolean enabled;

	public DBConnectionPool(ConnectionParam param) {

		this.param = param;
		int initNum = param.getMinConnectNum();
		permConnList = new ArrayList<ProxyConnection>(initNum);
		tempConnList = new ConcurrentLinkedQueue<ProxyConnection>();
		while (initNum-- > 0) {
			ProxyConnection conn = initConnection();
			if (conn == null) {
				throw new RuntimeException("initialize Connection Fail");
			}
			permConnList.add(conn);
		}
		recycleThread = new PoolRecycleThread();
		recycleThread.setDaemon(true);
		recycleThread.setName(POOL_RECYCLE_THREAD_NAME);
		recycleThread.start();

		enabled = true;
	}

	/**
	 * 初始化_Connection对象
	 * 
	 * @return
	 */
	private ProxyConnection initConnection() {
		ProxyConnection proxyConn = null;
		Connection conn = null;
		try {
			Class.forName(param.getDirver());
			conn = DriverManager.getConnection(param.getUrl(), param.getUser(),
					param.getPassword());
			proxyConn = new ProxyConnection(conn) {

				@Override
				public Connection refereConnection() {
					try {
						return super.refereConnection();
					} catch (Exception e) {
						e.printStackTrace();
						return null;
					} finally {
						// 唤醒可能因调用getConnection而处于等待状态的线程
						synchronized (lock) {
							lock.notifyAll();
						}
						// 若当前对象处于tempConnList,唤醒可能处于wait状态的资源回收线程
						synchronized (recycleThread) {
							recycleThread.notifyAll();
						}
					}
				}
			};
		} catch (Exception e) {
			e.printStackTrace();
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e1) {
					logger.log(Level.WARNING, e.getLocalizedMessage(), e);
				}
				conn = null;
			}
		}
		return proxyConn;
	}

	/**
	 * 获取连接池中可用的Connection对象
	 * 
	 * @return
	 */
	private Connection getConnectionFromPool() {

		// 查询常驻内存连接对象
		int permConnSize = permConnList.size();
		for (int i = 0; i < permConnSize; i++) {
			ProxyConnection _conn = permConnList.get(cursor);
			cursor = (cursor + 1) % permConnSize;

			if (_conn.hasReference()) {
				continue;
			}

			synchronized (_conn) {
				if (_conn.hasReference()) {
					continue;
				}
				Connection proxyConn = _conn.refereConnection();
				try {
					// 异常场景下导致数据库连接无效,则尝试重新建立
					if (!proxyConn.isValid(param.getConnectTimeout())) {
						proxyConn = null;
						_conn.close();

						// 若能重新建立连接则替换原失效连接
						ProxyConnection conn1 = initConnection();
						if (conn1 == null) {
							continue;
						}
						proxyConn = conn1.refereConnection();
						permConnList.set((cursor - 1 + permConnSize)
								% permConnSize, conn1);
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}
				if (proxyConn != null) {
					return proxyConn;
				}
			}
		}

		// 查询新增的临时连接对象
		for (ProxyConnection conn : tempConnList) {
			if (conn.hasReference()) {
				continue;
			}
			synchronized (conn) {
				if (!conn.hasReference()) {
					return conn.refereConnection();
				}
			}
		}

		// 未达连接上限,新增一连接对象
		if (permConnSize + tempConnList.size() >= param.getMaxConnectNum()) {
			return null;
		}
		synchronized (tempConnList) {
			if (permConnSize + tempConnList.size() < param.getMaxConnectNum()) {
				ProxyConnection conn = initConnection();
				if (conn != null) {
					tempConnList.add(conn);
					return conn.refereConnection();
				}
			}
		}
		return null;
	}

	/**
	 * <p>
	 * 从数据库中获取可用的连接
	 * </p>
	 * <p>
	 * 若首次成功获取到数据库连接,则将唤醒连接回收线程.带超时connectTimeout之后自动释放资源
	 * </p>
	 * <p>
	 * 若首次未获取连接,则等待connectTimeout.此期间若回收了连接资源则唤醒阻塞的线程以尝试再次获取连接
	 * </p>
	 * <p>
	 * 此次若成功获取到数据库连接,则将唤醒连接回收线程.带超时connectTimeout之后自动释放资源
	 * </p>
	 * <p>
	 * 若还未获取到可用连接,返回null
	 * </p>
	 * 
	 * @return
	 * @throws SQLException
	 */
	public Connection getConnection() throws SQLException {

		if (!enabled) {
			logger.log(Level.WARNING, "Database Pool is disable now!");
			return null;
		}
		Connection conn = getConnectionFromPool();
		// 尝试进行二次获取
		if (conn == null) {
			try {
				synchronized (lock) {
					lock.wait(param.getConnectTimeout());
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			conn = getConnectionFromPool();
		}
		if (conn != null) {
			conn.setAutoCommit(param.isAutoCommit());
			if (!conn.getAutoCommit()) {
				conn.rollback();
			}
			// 唤醒连接池回收线程
			synchronized (recycleThread) {
				recycleThread.notifyAll();
			}
		}
		return conn;
	}

	/**
	 * 释放连接资源
	 */
	public synchronized void release() {
		enabled = false;
		for (ProxyConnection con : permConnList) {
			con.close();
		}
		permConnList.clear();
		for (ProxyConnection con : tempConnList) {
			con.close();
		}
		tempConnList.clear();

		synchronized (lock) {
			lock.notifyAll();
		}

		synchronized (recycleThread) {
			recycleThread.notifyAll();
		}
	}

	/**
	 * 数据库链接吃Connection对象回收线程
	 * 
	 * <pre>
	 * 自行回收那些被上时间占用却处于空闲状态的数据库链接
	 * </pre>
	 * 
	 * @author 郑俊伟
	 * 
	 */
	private class PoolRecycleThread extends Thread {

		@Override
		public void run() {

			while (enabled) {
				final int timeout = param.getConnectTimeout();// Connection超时时间
				int freeNum = 0;// 空闲资源数
				boolean freed = false;// 是否释放过资源

				for (ProxyConnection _conn : permConnList) {
					if (!_conn.hasReference()) {
						freeNum++;
						continue;
					}
					if (System.currentTimeMillis() - _conn.getLastAccessTime() <= timeout) {
						continue;
					}

					try {
						Connection conn = _conn.getConnectionButUnrefer();
						if (!conn.getAutoCommit()) {
							conn.rollback();
						}
						conn.close();
						freeNum++;
						logger.log(Level.WARNING,
								"timeout,Perm connection auto closed");
					} catch (Exception e) {
						e.printStackTrace();
					}
					freed = true;
				}

				Iterator<ProxyConnection> iterator = tempConnList.iterator();
				while (iterator.hasNext()) {
					ProxyConnection _conn = iterator.next();
					if (_conn.hasReference()
							&& (System.currentTimeMillis()
									- _conn.getLastAccessTime() > timeout)) {
						try {
							Connection conn = _conn.getConnectionButUnrefer();
							if (!conn.getAutoCommit()) {
								conn.rollback();
							}
							conn.close();
							logger.log(Level.WARNING,
									"timeout,Temp connection auto closed");
						} catch (Exception e) {
							e.printStackTrace();
						}
						freed = true;
					} else if (!_conn.hasReference()
							&& (System.currentTimeMillis()
									- _conn.getLastAccessTime() > timeout)) {
						_conn.close();
						iterator.remove();
						freed = true;
					}
				}
				// 若存在被回收的连接资源,则唤醒一次连接池中的lock对象
				if (freed) {
					synchronized (lock) {
						lock.notifyAll();
					}
				}
				synchronized (PoolRecycleThread.this) {
					try {
						if (tempConnList.size() == 0
								&& freeNum == permConnList.size()) {
							PoolRecycleThread.this.wait();
						} else {
							PoolRecycleThread.this.wait(timeout);
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			logger.log(Level.INFO, "Stop PoolRecycleThread");
		}
	}
}
